Working with DataRows

As you have seen, a collection of DataColumn objects represents the schema of a DataTable. In contrast, a collection of DataRow objects represents the actual data in the table. Thus, if you have 20 rows in the Inventory table of the AutoLot database, you can represent these records using 20 DataRow objects. Table 22-4 documents some (but not all) of the members of the DataRow type.

Table 22-4. Key Members of the DataRow Type

Members Meaning in Life
HasErrors GetColumnsInError() GetColumnError() ClearErrors() RowError The HasErrors property returns a Boolean value indicating whether there are errors in a DataRow. If so, you can use the GetColumnsInError() method to obtain the offending columns and GetColumnError()to obtain the error description. Similarly, you can use the ClearErrors() method to remove each error listing for the row. The RowError property allows you to configure a textual description of the error for a given row.
ItemArray This property gets or sets all of the column values for this row using an array of objects.
RowState You use this property to pinpoint the current state of the DataRow in the DataTable containing the DataRow, using values of the RowState enumeration (e.g., a row can be flagged as new, modified, unchanged, or deleted).
Table You use this property to obtain a reference to the DataTable containing this DataRow.
AcceptChanges() RejectChanges() These methods commit or reject all changes made to this row since the last time AcceptChanges() was called.
BeginEdit() EndEdit() CancelEdit() These methods begin, end, or cancel an edit operation on a DataRow object.
Delete() This method marks a row you want to remove when the AcceptChanges() method is called.
IsNull() This method gets a value indicating whether the specified column contains a null value.

Working with a DataRow is a bit different from working with a DataColumn; you cannot create a direct instance of this type because there is no public constructor:

// Error! No public constructor!
DataRow r = new DataRow();

Instead, you obtain a new DataRow object from a given DataTable. For example, assume you wish to insert two rows in the Inventory table. The DataTable.NewRow() method allows you to obtain the next slot in the table, at which point you can fill each column with new data using the type indexer. When doing so, you can specify either the string name assigned to the DataColumn or its (zero-based) ordinal position:

static void FillDataSet(DataSet ds)
{
...
    // Now add some rows to the Inventory Table.
    DataRow carRow = inventoryTable.NewRow();
    carRow["Make"] = "BMW";
    carRow["Color"] = "Black";
    carRow["PetName"] = "Hamlet";
    inventoryTable.Rows.Add(carRow);

    carRow = inventoryTable.NewRow();
    // Column 0 is the autoincremented ID field,
    // so start at 1.
    carRow[1] = "Saab";
    carRow[2] = "Red";
    carRow[3] = "Sea Breeze";
    inventoryTable.Rows.Add(carRow);
}

Note If you pass the DataRow’s indexer method an invalid column name or ordinal position, you will receive a runtime exception.

At this point, you have a single DataTable containing two rows. Of course, you can repeat this general process to create a number of DataTables to define the schema and data content. Before you insert the inventoryTable object into your DataSet object, you should check out the all-important RowState property.

Understanding the RowState Property

The RowState property is useful when you need to identify programmatically the set of all rows in a table that have changed from their original value, have been newly inserted, and so forth. You can assign this property any value from the DataRowState enumeration, as shown in Table 22-5.

Table 22-5. Values of the DataRowState Enumeration

Value Meaning in Life
Added The row has been added to a DataRowCollection, and AcceptChanges() has not been called.
Deleted The row has been marked for deletion using the Delete() method of the DataRow, and AcceptChanges() has not been called.
Detached The row has been created but is not part of any DataRowCollection. A DataRow is in this state immediately after it has been created, but before it is added to a collection. It is also in this state if it has been removed from a collection.
Modified The row has been modified, and AcceptChanges() has not been called.
Unchanged The row has not changed since AcceptChanges() was last called.

When you manipulate the rows of a given DataTable programmatically, the RowState property is set automatically. For example, add a new method to your Program class, which operates on a local DataRow object, printing out its row state along the way:

private static void ManipulateDataRowState()
{
    // Create a temp DataTable for testing.
    DataTable temp = new DataTable("Temp");
    temp.Columns.Add(new DataColumn("TempColumn", typeof(int)));
    
    // RowState = Detached (i.e. not part of a DataTable yet)
    DataRow row = temp.NewRow();
    Console.WriteLine("After calling NewRow(): {0}", row.RowState);
    
    // RowState = Added.
    temp.Rows.Add(row);
    Console.WriteLine("After calling Rows.Add(): {0}", row.RowState);
    
    // RowState = Added.
    row["TempColumn"] = 10;
    Console.WriteLine("After first assignment: {0}", row.RowState);
    
    // RowState = Unchanged.
    temp.AcceptChanges();
    Console.WriteLine("After calling AcceptChanges: {0}", row.RowState);
    
    // RowState = Modified.
    row["TempColumn"] = 11;
    Console.WriteLine("After first assignment: {0}", row.RowState);
    
    // RowState = Deleted.
    temp.Rows[0].Delete();
    Console.WriteLine("After calling Delete: {0}", row.RowState);
}

The ADO.NET DataRow is smart enough to remember its current state of affairs. Given this, the owning DataTable is able to identify which rows have been added, updated, or deleted. This is a key feature of the DataSet because, when it comes time to send updated information to the data store, only the modified data is submitted.

Understanding the DataRowVersion Property

Beyond maintaining the current state of a row with the RowState property, a DataRow object maintains three possible versions of the data it contains using the DataRowVersion property. When a DataRow object is first constructed, it contains only a single copy of data, represented as the current version. However, as you programmatically manipulate a DataRow object (using various method calls), additional versions of the data spring to life. Specifically, you can set the DataRowVersion to any value of the related DataRowVersion enumeration (see Table 22-6).

Table 22-6. Values of the DataRowVersion Enumeration

Value Meaning in Life
Current This represents the current value of a row, even after changes have been made.
Default This is the default version of DataRowState. For a DataRowState value of Added, Modified, or Deleted, the default version is Current. For a DataRowState value of Detached, the version is Proposed.
Original This represents the value first inserted into a DataRow or the value the last time AcceptChanges() was called.
Proposed This is the value of a row currently being edited due to a call to BeginEdit().

As suggested in Table 22-6, the value of the DataRowVersion property is dependent on the value of the DataRowState property in many cases. As mentioned previously, the DataRowVersion property will be changed behind the scenes when you invoke various methods on the DataRow (or, in some cases, the DataTable) object. Here is a breakdown of the methods that can affect the value of a row’s DataRowVersion property:

Yes, this is a bit convoluted, not least because a DataRow might or might not have all versions at any given time (you’ll receive runtime exceptions if you attempt to obtain a row version that is not currently tracked). Regardless of the complexity, given that the DataRow maintains three copies of data, it becomes simple to build a front end that allows an end user to alter values, change his or her mind and roll back values, or commit values permanently. You’ll see various examples of manipulating these methods over the remainder of this chapter.